function Level(processing, levelObjects) {

	// Currently selected thing. Can be null if nothing is selected.
	this.selected = null;

	// The static and dynamic objects in this level.
	// This object encapsulates the gameplay for an individual level,
	// whereas the Level class is the logic for all levels.
	
	// The static and dynamic objects are copied into variables on this
	// class to avoid overly long calls to them.
	this.dynamicObjects = levelObjects.dynamicObjects;	
	this.staticObjects = levelObjects.staticObjects;	
	this.allObjects = levelObjects.allObjects;	
	this.winConditions = levelObjects.winConditions;
	
	// Each level has a different background color
	// (TODO(lilli): later a different background image)
	this.r = levelObjects.r;
	this.g = levelObjects.g;
	this.b = levelObjects.b;
	
	this.clearAlpha = 0;
	
	// Every level knows whether it has been completed yet.
	this.levelCleared = false;
	this.winPending = false;

	Level.prototype.render = function(processing) {
	 	// Reset all the win conditions, because they need
	 	// to be rechecked each frame.
	 	for (var i=0; i<this.winConditions.length; ++i) {
 			this.winConditions[i].reset();
 		}
	 		
 		// Clear the background by rendering a transparent quad over the
 		// background image.
 		for (var x = 0; x < $x; x += 75) {
 			for (var y = 0; y < $y; y += 75) {
 				if (
 					(x == 0 && y == 0) ||
 					(x == 0 && y > $y - 80) || 
 					(x > $x - 80 && y == 0) ||
 					(x > $x - 80 && y > $y - 80)
 					) {
 					processing.image($borderTile0, x, y);
 				} else if (x == 0 || y == 0 || x > $x - 85 || y > $y - 85) {
 					processing.image($borderTile1, x, y);
 				} else {
 					processing.image($brickTiles, x, y);
 				}
 			}
 		}
 		processing.fill(this.r, this.g, this.b, 50);
 		processing.quad(
 			0, 0,
 			$x, 0,
 			$x, $y,
 			0, $y	
 		);
 		
 		// Render all the static objects.
		for (var i=0; i<this.staticObjects.length; i++) {
		  this.staticObjects[i].update(this.allObjects);
		  this.staticObjects[i].render(processing);
 		}
 		for (var i=0; i< this.winConditions.length; i++) {
		  this.winConditions[i].render(processing);
 		}
 		
 		// Render the available objects area.
 		processing.fill(this.r, this.g, this.b);
 		processing.quad(
 			0, $y,
 			$x, $y,
 			$x, $y + $availableHeight,
 			0, $y + $availableHeight);
 		
 		// Render all the dynamic objects.
 		var nextAvailableObjectXCoordinate = 0;		
 		for (var i=0; i<this.dynamicObjects.length; i++) {	
 			if (this.dynamicObjects[i].isPlaced) {
 			
 				// Render all objects placed in the world
 				this.dynamicObjects[i].render(processing);
 			} else {
 			
 				// For objects yet to be placed, force them to exist in the
 				// available objects area.
 				this.dynamicObjects[i].setPosition(
 					nextAvailableObjectXCoordinate + this.dynamicObjects[i].halfLength + $availableSpacing,
 					$y + this.dynamicObjects[i].halfLength + $availableSpacing);
 			
 				this.dynamicObjects[i].render(processing);
 				nextAvailableObjectXCoordinate += (this.dynamicObjects[i].halfLength * 2) + $availableSpacing;
 			}					
 		}
 		
 		// Now that everything has been udpated, check to see if this level
 		// has been won.
 		this.checkLevelOver();
 		
 		// If the level has been won, fade out the background.
 		if (this.winPending) {
 			this.fadeBackground();
 			processing.fill(150, 255, 0, this.clearAlpha);
 			processing.pushMatrix();
 			processing.scale(8, 8);
 			processing.text("Stage Clear!", 15, 20);
 			processing.popMatrix();
 		}
	}
	
	Level.prototype.fadeBackground = function() {
		if (this.r > 0) {
			this.r = Math.max(0, this.r - 1);
		}
		if (this.g > 0) {
			this.g = Math.max(0, this.g - 1);
		}
		if (this.b > 0) {
			this.b = Math.max(0, this.b - 1);
		}
		if (this.clearAlpha < 255) {
			this.clearAlpha = Math.min(255, this.clearAlpha + 15);
		}
	}
	
	Level.prototype.checkLevelOver = function() {
 		// We want this check in the update loop instead. TODO(lilli): move it.
 		won = true;
 		for (var i=0; i<this.winConditions.length; ++i) {
 			if (!this.winConditions[i].satisfied) {
 				won = false;
 			}
 		}
		// Don't actually clear the level yet; we want to
		// savor the victory (or the bugs). Level will clear
		// on next mouse click.
 		this.winPending = won;
	}
	
	// --- Mouse Handlers ---
	
	Level.prototype.mousePressed = function(mouseX, mouseY) {	
		
                // If win is pending, mouse click finalizes it.
	        if (this.winPending) {
		  this.levelCleared = true;
		  return;
		}
		// TODO(lilli): remove this code once we have a better way to win levels.
		// Send the mouse click to the win conditions. For now this is necessary
 		// because the win condition for targets is that they are clicked, but
 		// that's kind of silly. We can probably remove this once the laser can
 		// interact with targets nicely.
 		for (var i=0; i<this.winConditions.length; ++i) {
 			this.winConditions[i].processMouse(mouseX, mouseY);
 		}
 		// We want this check in the update loop instead. TODO(lilli): move it.
		this.checkLevelOver();
	        if (this.winPending) {
		  this.levelCleared = true;
		  return;
		}
		
		// First check to see if the user is placing a new item from the
		// available objects list.
		for (var i=0; i<this.dynamicObjects.length; i++) {
 			if (this.dynamicObjects[i].collides(mouseX, mouseY)) {
 				
 				// Unselect the currently selected thing
 				if (this.selected != null) {
 					this.selected.isSelected = false;
 				}
 			
 				// Select the new thing.
 				this.dynamicObjects[i].isSelected = true;
 				this.selected = this.dynamicObjects[i];
 				
 				// Move this object from the available list into the
 				// placed list.
 				this.dynamicObjects[i].isPlaced = true;
 				
 				return;
 			}
 		}	
 			 		
 		// If this mouse click didn't collide with any object, then
 		// unselect the currently selected thing.
 		if (this.selected != null) {
 			this.deselectAll();
 		}	
	}
	
	Level.prototype.mouseReleased = function() {
		// If the user had an object selected, figure out
		// whether the object was dropped on the main canvas or
		// the available object area and snap the object to one
		// or the other.
		
		if (this.selected != null) {
		
			// Constrain first off the top and sides
			if (this.selected.px < this.selected.halfLength) {
				this.selected.px = this.selected.halfLength;
			}
			if (this.selected.px > $x - this.selected.halfLength) {
				this.selected.px = $x - this.selected.halfLength;
			}
			if (this.selected.py < this.selected.halfLength) {
				this.selected.py = this.selected.halfLength;
			}
			
			// If the object is more than half in the play area, snap it there.
			// Otherwise return it to its unplaced state.
			if (this.selected.py > $y) {
				this.selected.isPlaced = false;
				this.deselectAll();	
			} else if (this.selected.py > $y - this.selected.halfLength) {
				this.selected.py = $y - this.selected.halfLength;
			}	
			
			this.deselectAll();
		}
	}
	
	Level.prototype.mouseDragged = function(mouseX, mouseY) {
		if (this.selected == null) {
 			return;
 		}
 		this.selected.setPosition(mouseX, mouseY);
	}
	
	Level.prototype.keyPressed = function(keyCode) {
		// Levels ignore key presses.
	}
	
	// --- Selection ---
	
	Level.prototype.deselectAll = function() {
		if (this.selected == null) {
			return;
		}
		this.selected.isSelected = false;
		this.selected = null;
	}
}
